/*!
    \copyright  Copyright (c) 2024 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    @{
        \ingroup    adkfs
        \brief      Provides a layer over the rafs filesystem to match the AdkFs interface.

*/
#include "adkfs_rafs_logging.h"
#include "adkfs_provider_if.h"
#include "adkfs_sysmon_if.h"
#include "adkfs_rafs_provider.h"
#include "adkfs_types.h"
#include <rafs.h>

#include <panic.h>

#include <errno.h>
#include <string.h>

#define RAFS_ASSERT(rafs_result, ...)  do{ if (rafs_result != RAFS_OK) DEBUG_LOG_PANIC(__VA_ARGS__); } while(0)

static void adkFsRafs_InitHandler(Task task, MessageId id, Message message);

static TaskData rafs_init_task = {.handler = adkFsRafs_InitHandler };
static Task provider_init_task = NULL;

static rafs_errors_t last_rafs_errno = RAFS_OK;

static int32_t rafs_error_to_errno(rafs_errors_t rafs_error)
{
    int32_t errno = 0;
    last_rafs_errno = rafs_error;

    switch(rafs_error)
    {
        case RAFS_MAX_FILES_OPENED:
            errno = ENFILE;
        break;

        case RAFS_BAD_ID:
        /*lint -fallthrough */
        case RAFS_NOT_SEEKABLE:
            errno = EBADF;
        break;

        case RAFS_INVALID_CHARACTER:
        /*lint -fallthrough */
        case RAFS_INVALID_PATH:
        /*lint -fallthrough */
        case RAFS_FILE_NOT_FOUND:
            errno = ENOENT;
        break;

        case RAFS_FILE_SYSTEM_FULL:
            errno = ENOMEM;
        break;

        case RAFS_FILE_FULL:
            errno = ENOSPC;
        break;

        case RAFS_INVALID_LENGTH:
            errno = ENAMETOOLONG;
        break;

        case RAFS_FILE_EXISTS:
            errno = EEXIST;
        break;

        case RAFS_ALREADY_MOUNTED:
        /*lint -fallthrough */
        case RAFS_STILL_MOUNTED:
        /*lint -fallthrough */
        case RAFS_FILE_STILL_OPEN:
        /*lint -fallthrough */
        case RAFS_DIRECTORY_OPEN:
        /*lint -fallthrough */
        case RAFS_BUSY:
            errno = EBUSY;
        break;

        case RAFS_INVALID_FAT:
        /*lint -fallthrough */
        case RAFS_INVALID_PARTITION:
        /*lint -fallthrough */
        case RAFS_NO_PARTITION:
        /*lint -fallthrough */
        case RAFS_NOT_MOUNTED:
            errno = ENOTDIR;
        break;

        case RAFS_INVALID_SEEK:
            errno = EINVAL;
        break;

        case RAFS_FAT_WRITE_FAILED:
        /*lint -fallthrough */
        case RAFS_NO_MORE_FILES:
        /*lint -fallthrough */
        case RAFS_NO_MORE_DATA:
        /*lint -fallthrough */
        case RAFS_UNSUPPORTED_IOC:
        /*lint -fallthrough */
        case RAFS_LOW_POWER:
        /*lint -fallthrough */
        case RAFS_FILE_SYSTEM_CLEAN:
            errno = EIO;
        break;

        case RAFS_OK:
            errno = 0;
        break;

        case RAFS_LAST:
            Panic();

        /* default: is skipped on purpose.
         * If new errors are added we want the compilation to fail so they can be mapped
         */
    }

    return errno;
}

static int32_t AdkFsRafs_Fopen(rafs_file_t** handle, const char *path, adkfs_access_flags_t access)
{
    rafs_errors_t returncode = RAFS_OK;
    rafs_mode_t access_flags = 0;
    rafs_file_t* rafs_handle = PanicUnlessNew(rafs_file_t);
    memset(rafs_handle, 0, sizeof(*rafs_handle));

    switch(access)
    {
        case ADKFS_FOPEN_READ:
        {
            access_flags = RAFS_RDONLY;
            returncode = Rafs_Open(NULL, path, access_flags, rafs_handle);
        }
        break;

        case ADKFS_FOPEN_WRITE:
        {
            access_flags = RAFS_WRONLY;
            returncode = Rafs_Open(NULL, path, access_flags, rafs_handle);
            if (RAFS_FILE_EXISTS == returncode)
            {
                returncode = Rafs_Remove(path);
                if (RAFS_OK == returncode)
                {
                    returncode = Rafs_Open(NULL, path, access_flags, rafs_handle);
                }
            }
        }
        break;

        default:
            DEBUG_LOG_PANIC("AdkFs_Fopen: Unhandled access flags = enum:adkfs_access_flags_t:0x%08X", access);
            break;
    }

    if(RAFS_OK == returncode)
    {
        *handle = rafs_handle;
    }
    else
    {
        free(rafs_handle);
        *handle = NULL;
    }

    return rafs_error_to_errno(returncode);
}

static int32_t AdkFsRafs_Fsize(const char *path, size_t *fsize)
{
    return rafs_error_to_errno(Rafs_Fsize(path, fsize));
}

static int32_t AdkFsRafs_Fmap(rafs_file_t* handle, const void **fbuffer)
{
    UNUSED(handle);
    UNUSED(fbuffer);
    return EOPNOTSUPP;
}

static int32_t AdkFsRafs_Funmap(rafs_file_t* handle)
{
    UNUSED(handle);
    return EOPNOTSUPP;
}

static int32_t AdkFsRafs_Fseek(rafs_file_t* handle, size_t offset)
{
    return rafs_error_to_errno(Rafs_SetPosition(*handle, offset));
}

static int32_t AdkFsRafs_Ftell(rafs_file_t* handle, size_t *offset)
{
    return rafs_error_to_errno(Rafs_GetPosition(*handle, offset));
}

static int32_t AdkFsRafs_Fread(rafs_file_t* handle, void *buf_ptr, size_t read_size, size_t *bytes_read)
{
    return rafs_error_to_errno(Rafs_Read(*handle, buf_ptr, read_size, bytes_read));
}

static int32_t AdkFsRafs_Fwrite(rafs_file_t* handle, const void *buf_ptr, size_t write_size, size_t *bytes_written)
{
    return rafs_error_to_errno(Rafs_Write(*handle, buf_ptr, write_size, bytes_written));
}

static int32_t AdkFsRafs_Fclose(rafs_file_t* handle)
{
    int32_t errno = rafs_error_to_errno(Rafs_Close(*handle));
    free(handle);
    return errno;
}

static int32_t AdkFsRafs_Fdelete(const char *path)
{
    return rafs_error_to_errno(Rafs_Remove(path));
}

static void adkFsRafs_InitHandler(Task task, MessageId id, Message message)
{
    UNUSED(task);
    UNUSED(message);

    DEBUG_LOG_ALWAYS("adkFsRafs_InitHandler: enum:adkfs_provider_init_msgs_t:%d", id);
    switch(id)
    {
        case MESSAGE_RAFS_MOUNT_COMPLETE:
        {
            PanicNull(provider_init_task);
            MessageSend(provider_init_task, ADKFS_PROVIDER_INIT_MSGS_PROVIDER_INIT_COMPLETE, NULL);
            provider_init_task = NULL;
        }
        break;

        default:
            Panic();
    }
}

static void adkfs_SetAppIdleCallbackForRafsCompaction(adkfs_sysmon_if_t *sysmon_if)
{
    rafs_ioc_set_app_status_idle_cb_t app_idle_status_cb = {
        .appIsIdle = sysmon_if->systemIsIdle,
        .context = NULL
    };
    Rafs_IoControl(RAFS_IOC_SET_APP_STATUS_IDLE_CB, &app_idle_status_cb, sizeof(app_idle_status_cb), NULL, 0, NULL);
}

void adkFsRafs_Init(Task init_task)
{
    Rafs_Init(NULL);
    provider_init_task = init_task;
    if(Rafs_Mount(&rafs_init_task, ADKFS_RAFS_MOUNT_POINT) == RAFS_INVALID_FAT)
    {
        rafs_errors_t result = Rafs_Format(ADKFS_RAFS_MOUNT_POINT, format_normal);
        RAFS_ASSERT(result, "AdkFS [%u]: Failed to format RAFS at " ADKFS_RAFS_MOUNT_POINT, result);
        result = Rafs_Mount(&rafs_init_task, ADKFS_RAFS_MOUNT_POINT);
        RAFS_ASSERT(result, "AdkFS [%u]: Failed to mount RAFS at " ADKFS_RAFS_MOUNT_POINT, result);
    }

    adkfs_SetAppIdleCallbackForRafsCompaction(AdkFs_GetSysmonIf());
}

const adkfs_provider_if_t adkfs_rafs_if = {
    .root_path = "/ra",
    .Init = adkFsRafs_Init,
    .Fopen = AdkFsRafs_Fopen,
    .Fsize = AdkFsRafs_Fsize,
    .Fmap = AdkFsRafs_Fmap,
    .Funmap = AdkFsRafs_Funmap,
    .Fseek = AdkFsRafs_Fseek,
    .Ftell = AdkFsRafs_Ftell,
    .Fread = AdkFsRafs_Fread,
    .Fwrite = AdkFsRafs_Fwrite,
    .Fclose = AdkFsRafs_Fclose,
    .Fdelete = AdkFsRafs_Fdelete,
};

const adkfs_provider_if_t* AdkFsRafs_GetProvider(void)
{
    return &adkfs_rafs_if;
}